/**********************************************************************************
**
** Copyright (C) 2009 Jostein Bratlie
** Contact: jostein.bratlie@gmail.com | http://bratlie.info | http://episteme.hin.no
**
** This file is part of GMQtExt, a GMlib and Qt extension library
**
** GMQtExt is free software: you can redistribute it and/or modify
** it under the terms of the GNU Lesser General Public License as published by
** the Free Software Foundation, either version 3 of the License, or
** (at your option) any later version.
**
** GMQtExt is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
** GNU Lesser General Public License for more details.
**
** You should have received a copy of the GNU Lesser General Public License
** along with GMQtExt.  If not, see <http://www.gnu.org/licenses/>.
**
**********************************************************************************/

// Qt
#include <QtGui>

// GMlib includes
#include <gmCore>
#include <gmLightG>
#include <gmParametrics>
#include <gmVisualizers>
#include <gmMaterial>
#include <gmSceneObject>
#include <gmDisplay>


// Header
#include "glwidget.h"

namespace GMQtExt {

  GLWidget::GLWidget( QWidget* parent )
    : QGLWidget(
      QGLFormat(
        QGL::AlphaChannel |
        QGL::DoubleBuffer |
        QGL::DepthBuffer |
        QGL::Rgba |
        QGL::SampleBuffers |
        QGL::StereoBuffers
      ),
      parent
  ) {

    setFocusPolicy( Qt::StrongFocus );

    _hid_prevX = 0;
    _hid_prevY = 0;

    _shaded = true;
  }


  GLWidget::~GLWidget() {}


  void GLWidget::hidEventEdit() {

    // ERBS
    GMlib::PERBSCurve<float> *ecObj = dynamic_cast<GMlib::PERBSCurve<float>*>( _active_obj );
    GMlib::PERBSSurf<float> *esObj = dynamic_cast<GMlib::PERBSSurf<float>*>( _active_obj );
    GMlib::PERBSTriangle<float> *etObj = dynamic_cast<GMlib::PERBSTriangle<float>*>( _active_obj );

    // Bezier
    GMlib::PBezierCurve<float> *bcObj = dynamic_cast<GMlib::PBezierCurve<float>*>( _active_obj );
    GMlib::PBezierSurf<float> *bsObj = dynamic_cast<GMlib::PBezierSurf<float>*>( _active_obj );
    GMlib::PBezierTriangle<float> *btObj = dynamic_cast<GMlib::PBezierTriangle<float>*>( _active_obj );

    // Arc
    GMlib::PArc<float> *acObj = dynamic_cast<GMlib::PArc<float>*>( _active_obj );

    // ERBS
    if( ecObj ) {

      if( ecObj->isLocalPatchesVisible() )
        ecObj->hideLocalPatches();
      else
        ecObj->showLocalPatches();
    }
    else if( esObj ) {

      if( esObj->isLocalPatchesVisible() )
        esObj->hideLocalPatches();
      else
        esObj->showLocalPatches();
    }
    else if( etObj ) {
      if( etObj->isLocalPatchesVisible() )
        etObj->hideLocalPatches();
      else
        etObj->showLocalPatches();
    }
    // Bezier
    else if( bcObj ) {

      GMlib::PERBSCurve<float> *parent = dynamic_cast<GMlib::PERBSCurve<float>*>( bcObj->getParent() );
      if( parent ) {

        if( bcObj->toggleCollapsed() )
          bcObj->hideSelectors();
        else
          bcObj->showSelectors();
      }
      else {

        if( bcObj->isSelectorsVisible() )
          bcObj->hideSelectors();
        else
          bcObj->showSelectors();
      }
    }
    else if( bsObj ) {

      GMlib::PERBSSurf<float> *parent = dynamic_cast<GMlib::PERBSSurf<float>*>( bsObj->getParent());
      if( parent ) {

        if( bsObj->toggleCollapsed() )
          bsObj->hideSelectors();
        else
          bsObj->showSelectors(true);
      }
      else {

        if( bsObj->isSelectorsVisible() )
          bsObj->hideSelectors();
        else
          bsObj->showSelectors(true);
      }
    }
    else if( btObj ) {

      GMlib::PERBSTriangle<float> *parent = dynamic_cast<GMlib::PERBSTriangle<float>*>( btObj->getParent() );
      if( parent ) {

        if( btObj->toggleCollapsed() )
          btObj->hideSelectors();
        else
          btObj->showSelectors(true);
      }
      else {

        if( btObj->isSelectorsVisible() )
          btObj->hideSelectors();
        else
          btObj->showSelectors(true);
      }
    }
    else if( acObj ) {

      acObj->toggleCollapsed();
    }
  }


  void GLWidget::hidEventReplotQuickHigh() {

    GMlib::PCurve<float> *curve = dynamic_cast<GMlib::PCurve<float>*>( _active_obj );
    GMlib::PSurf<float> *surf = dynamic_cast<GMlib::PSurf<float>*>( _active_obj );

    if( curve ) {

      GMlib::PERBSCurve<float> *erbs = dynamic_cast<GMlib::PERBSCurve<float>*>(curve);
      if( erbs )
        erbs->replot( (erbs->getLocalPatches().getDim()-1)*5 + 1 );
      else
        curve->replot( 2500 );
    }
    else if( surf ) {

      GMlib::PERBSSurf<float> *erbs = dynamic_cast<GMlib::PERBSSurf<float>*>(surf);
      if( erbs )
        erbs->replot( (erbs->getLocalPatches().getDim1()-1)*5 + 1, (erbs->getLocalPatches().getDim2()-1)*5 + 1 );
      else
        surf->replot( 50, 50 );
    }
  }


  void GLWidget::hidEventReplotQuickLow() {

    GMlib::PCurve<float> *curve = dynamic_cast<GMlib::PCurve<float>*>( _active_obj );
    GMlib::PSurf<float> *surf = dynamic_cast<GMlib::PSurf<float>*>( _active_obj );

    if( curve ) {

      GMlib::PERBSCurve<float> *erbs = dynamic_cast<GMlib::PERBSCurve<float>*>(curve);
      if( erbs )
        erbs->replot( erbs->getLocalPatches().getDim() );
      else
        curve->replot( 100 );
    }
    else if( surf ) {

      GMlib::PERBSSurf<float> *erbs = dynamic_cast<GMlib::PERBSSurf<float>*>(surf);
      if( erbs )
        erbs->replot( erbs->getLocalPatches().getDim1(), erbs->getLocalPatches().getDim2() );
      else
        surf->replot( 10, 10 );
    }
  }


  void GLWidget::hidEventSelectObjTree( GMlib::SceneObject *obj ) {

    // Select Object ?
    GMlib::Camera *cam   = dynamic_cast<GMlib::Camera*>( obj );
    GMlib::Light  *light = dynamic_cast<GMlib::Light*>( obj );
    if( cam );
    else if( light );
    else {
      obj->setSelected( true );
      _sel_objs += obj;
    }

    // Recursive Propagation
    for( int i = 0; i < obj->getChildren().getSize(); i++ )
      hidEventSelectObjTree( (obj->getChildren())[i] );
  }


  void GLWidget::hidEventToggleShadingMode() {

    _shaded = !_shaded;
    if(!_shaded)
      glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
    else
      glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
  }


  void GLWidget::clear() {

    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  }


  void GLWidget::glDraw() {

    makeCurrent();
    paintGL();
  }


  void GLWidget::initializeGL() {

    GMlib::GMWindow::init();
    glClearColor( 0.5f, 0.5f, 0.5f, 1.0f );
    glLightModeli(GL_LIGHT_MODEL_TWO_SIDE,GL_TRUE);
    glShadeModel(GL_SMOOTH);
    glEnable(GL_DEPTH_TEST);
    glEnable(GL_LIGHTING);
  }


  void GLWidget::initScene() {

    // Insert a light
    GMlib::Point3D<GLfloat> init_light_pos( 2.0, 4.0, 10 );
    insertLight( new GMlib::PointLight( GMlib::GMcolor::White, GMlib::GMcolor::White, GMlib::GMcolor::White, init_light_pos ), false );

    // Insert Sun
    insertSun();

    // Camera
    GMlib::Point3D<float> init_cam_pos(  0.0f, 0.0f, 0.0f );
    GMlib::Vector3D<float> init_cam_dir( 0.0f, 1.0f, 0.0f );
    GMlib::Vector3D<float> init_cam_up(  0.0f, 0.0f, 1.0f );


    GMlib::Camera *cam = new GMlib::Camera( init_cam_pos, init_cam_dir, init_cam_up );
    cam->rotateGlobal( GMlib::Angle(-45), GMlib::Vector3D<float>( 1.0f, 0.0f, 0.0f ) );
    cam->translate( GMlib::Vector3D<float>( 0.0f, -20.0f, 20.0f ) );

    insertCamera( cam );

    setViewSet( _cameras.index( cam ) );
  }

  void GLWidget::insert( GMlib::SceneObject *obj ) {

    GMlib::Scene::insert( obj );
  }


  void GLWidget::keyPressEvent( QKeyEvent* e ) {

    if( e->key() == Qt::Key_A ) {

      if( _active_obj || _sel_objs.getSize() > 0 ) {

        int no_objs = _sel_objs.getSize();

        // Remove Selections
        for( int i = 0; i < no_objs; i++ )
            _sel_objs[i]->setSelected( false );

        // Pop all array elements
        for( int i = 0; i < no_objs; i++ )
          _sel_objs.pop();

        if( _active_obj ) {
          _active_obj->setSelected( false );
          _active_obj = 0x0;
        }
      }
      else {

        for( int i = 0; i < getSize(); i++ ) {

          hidEventSelectObjTree( operator[](i) );
        }

        if( _sel_objs[0] )
          _active_obj = _sel_objs[0];
      }
    }

    else if( e->key() == Qt::Key_E ) {

      hidEventEdit();
    }

    else if( e->key() == Qt::Key_P ) {


      if( e->modifiers() == Qt::ShiftModifier )
        hidEventReplotQuickHigh();
      else if( e->modifiers() == Qt::NoModifier )
        hidEventReplotQuickLow();
    }


    else if( e->key() == Qt::Key_Q ) {

      // Ask for confirmation on clear
      QMessageBox::StandardButton choice = QMessageBox::question( this, "Quit?", "Sure you want to quit?", QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes );
      switch( choice ) {

        case QMessageBox::No:

          return;
          break;

        case QMessageBox::Yes:
        default:

          // Remove all Selections
          QApplication::quit();
          break;
      }
    }

    else if( e->key() == Qt::Key_R ) {

      toggleRun();
    }


    else if( e->key() == Qt::Key_Z ) {

      hidEventToggleShadingMode();
    }


    // Update GL
    updateGL();
  }


  void GLWidget::keyReleaseEvent( QKeyEvent* /*e*/ ) {



    // Update GL
    updateGL();
  }


  void GLWidget::mouseDoubleClickEvent( QMouseEvent* e ) {

    int x = e->x();
    int y = _h - e->y() - 1;



    // Store coords
    _hid_prevX = x;
    _hid_prevY = y;

    // Update GL
    updateGL();
  }


  void GLWidget::mouseMoveEvent( QMouseEvent *e ) {

    int x = e->x();
    int y = _h - e->y() - 1;

    if( e->buttons() == Qt::LeftButton && e->modifiers() == Qt::NoModifier ) {

      if( _move_border )
        moveBorder(x,y);
      else {
        float scale;				// Only to unify the speed of the camera
        if(_cameras[_active_cam]->isLocked())
          scale = M_2PI*_cameras[_active_cam]->getLockDist();
        else
          scale = Scene::getSphere().getRadius();

        float moveX = (x-_hid_prevX)*scale/_cameras[_active_cam]->getViewportW();
        float moveY = (_hid_prevY - y)* scale/_cameras[_active_cam]->getViewportH();

        GMlib::Vector2D<float> motion = GMlib::Vector2D<float>( moveX, moveY );
        _cameras[_active_cam]->move( motion );
      }
    }
    else if( e->buttons() == Qt::LeftButton && e->modifiers() == ( Qt::ControlModifier + Qt::AltModifier ) ) {

      GMlib::DisplayObject* tmp = dynamic_cast<GMlib::DisplayObject*>( _active_obj );
      if( tmp ) {

        double dh = _cameras[_active_cam]->deltaTranslate( tmp );
        GMlib::Vector3D<float> deltav( ( ( _hid_prevX - x ) * dh )* _cameras[_active_cam]->getSide() + ( ( y - _hid_prevY ) * dh ) * _cameras[_active_cam]->getUp() );

        if( deltav.getLength() < 1000.0 ) {

          tmp->scale( GMlib::Vector<float,3>( 1.0f + deltav[1] ) );
        }
      }
    }
    else if( e->buttons() == Qt::LeftButton && e->modifiers() == Qt::ShiftModifier ) {

      for( int i = 0; i < _sel_objs.getSize(); i++ ) {

        GMlib::DisplayObject* tmp = dynamic_cast<GMlib::DisplayObject*>( _sel_objs[i] );
        if( tmp ) {

          double dh = _cameras[_active_cam]->deltaTranslate( tmp );
          GMlib::Vector3D<float> deltav( ( ( _hid_prevX - x ) * dh )* _cameras[_active_cam]->getSide() + ( ( y - _hid_prevY ) * dh ) * _cameras[_active_cam]->getUp() );

          if( deltav.getLength() > SNAP && deltav.getLength() < 1000.0 ) {

            if( tmp->getTypeId() != GMlib::GM_SO_TYPE_SELECTOR ) {
              tmp->translateGlobal( deltav );
            }
            else if( tmp->getTypeId()== GMlib::GM_SO_TYPE_SELECTOR )
              tmp->editPos(deltav);
          }
        }
      }
    }
    else if( e->buttons() == Qt::LeftButton && e->modifiers() == Qt::ControlModifier ) {

      if( _active_obj ) {

        if( _active_obj->getTypeId() != GMlib::GM_SO_TYPE_SELECTOR ) {

          GMlib::UnitVector3D<float> rot_v = float( x - _hid_prevX ) * _cameras[_active_cam]->getUp() - float( _hid_prevY - y ) * _cameras[_active_cam]->getSide();
          GMlib::Angle ang( M_2PI * sqrt( pow( double( x - _hid_prevX) / _cameras[_active_cam]->getViewportW(), 2 ) + pow( double( _hid_prevY - y ) / _cameras[_active_cam]->getViewportH(), 2 ) ) );

          if( fabs( float(x-_hid_prevX) ) > POS_TOLERANCE || fabs( float(y-_hid_prevY) ) > POS_TOLERANCE )
            _active_obj->rotate( ang, rot_v );
        }
      }
    }



    // Store coords
    _hid_prevX = x;
    _hid_prevY = y;

    // Update GL
    updateGL();
  }


  void GLWidget::mousePressEvent( QMouseEvent *e ) {

    int x = e->x();
    int y = _h - e->y() - 1;

    GMlib::SceneObject *prev_active_obj = _active_obj;


    if ( GMWindow::find( x, y, _active_cam  ) ) {

      if( e->buttons() == Qt::RightButton ) {

        GMlib::SceneObject *sel_obj = _cameras[_active_cam]->findSelectObject( x, y, GMlib::GM_SO_TYPE_SELECTOR );	// Searching for selector object among every object
        if( !sel_obj )
          sel_obj = _cameras[_active_cam]->findSelectObject( x, y, -GMlib::GM_SO_TYPE_SELECTOR );

        if( sel_obj ) {

          // Remove all selections if no modifier is held
          if( e->modifiers() == Qt::NoModifier ) {

            int no_objs = _sel_objs.getSize();

            // Remove Selections
            for( int i = 0; i < no_objs; i++ )
                _sel_objs[i]->setSelected( false );

            // Pop all array elements
            for( int i = 0; i < no_objs; i++ )
              _sel_objs.pop();
          }

          switch( e->modifiers() ) {

            case Qt::NoModifier: {

              if( _cameras[_active_cam]->isLocked() )
                _cameras[_active_cam]->unLock();
            }
            case Qt::ShiftModifier: {
              // Register "Active" Object
              if( _active_obj == sel_obj ) {

                sel_obj->setSelected( false );
                _sel_objs.remove( sel_obj );

                if( _sel_objs.getSize() > 0 )
                  _active_obj = _sel_objs[0];
                else {
                  _active_obj = 0x0;
                }
              }
              else {

                sel_obj->setSelected( true );
                _sel_objs.insert( sel_obj );
                _active_obj = sel_obj;
              }
            } break;

            case Qt::ControlModifier: {

                _cameras[_active_cam]->lock( sel_obj );
            } break;

            default:
              break;

          }

          if( _active_obj != prev_active_obj )
            emit signActiveObjectChanged( _active_obj );
        }
        else{

          if( _cameras[_active_cam]->isLocked() )
            _cameras[_active_cam]->unLock();
          else
            _cameras[_active_cam]->lock((getSphereClean().getPos()-_cameras[_active_cam]->getPos())*_cameras[_active_cam]->getDir());
        }
      }
    }
    else if( _active_cam < 0 ) {

      if( e->buttons() == Qt::LeftButton )
        _move_border = true;

      _active_cam = 0;
    }


    // Store coords
    _hid_prevX = x;
    _hid_prevY = y;

    // Update GL
    updateGL();
  }


  void GLWidget::mouseReleaseEvent( QMouseEvent *e ) {

    int x = e->x();
    int y = _h - e->y() - 1;


    if( e->button() == Qt::LeftButton )
      _move_border = false;


    // Store coords
    _hid_prevX = x;
    _hid_prevY = y;

    // Update GL
    updateGL();
  }


  void GLWidget::paintEvent( QPaintEvent* /*e*/ ) {

    updateGL();
  }


  void GLWidget::paintGL() {

    display();
  }


  void GLWidget::resizeGL( int w, int h ) {

    reshape( w, h );
  }

  void GLWidget::setSelectedObject( GMlib::SceneObject *obj ) {

    int no_objs = _sel_objs.getSize();

    // Remove Selections
    for( int i = 0; i < no_objs; i++ )
        _sel_objs[i]->setSelected( false );

    // Pop all array elements
    for( int i = 0; i < no_objs; i++ )
      _sel_objs.pop();

    _active_obj = 0x0;

    if( !obj )
      return;

    obj->setSelected( true );
    _active_obj = obj;
    _sel_objs.insert( obj );
  }


  void GLWidget::swapBuffers() {

    QGLWidget::swapBuffers();
  }


  void GLWidget::timerEvent( QTimerEvent* /*e*/ ) {

    updateGL();
  }


  bool GLWidget::toggleRun() {

    bool started = GMlib::GMWindow::toggleRun();

    if( started )
      _timerID = startTimer(0);	// 0 gives idle(), otherwise in millisec.
    else
      killTimer(_timerID);

    return started;
  }


  void GLWidget::wheelEvent( QWheelEvent *e ) {

    int x = e->x();
    int y = _h - e->y() - 1;
    int delta = e->delta();


    // Pan "x" direction
    if( e->modifiers() == Qt::ControlModifier ) {

      float scale;				// Only to unify the speed of the camera
      if(_cameras[_active_cam]->isLocked())
        scale = M_2PI*_cameras[_active_cam]->getLockDist();
      else
        scale = GMlib::Scene::getSphere().getRadius();

      _cameras[_active_cam]->move(
        GMlib::Vector2D<float>( ( delta )*scale/_cameras[_active_cam]->getViewportH(), 0.0f )
      );
    }

    // Pan "y" direction
    else if( e->modifiers() == Qt::ShiftModifier ) {

      float scale;				// Only to unify the speed of the camera
      if(_cameras[_active_cam]->isLocked())
        scale = M_2PI*_cameras[_active_cam]->getLockDist();
      else
        scale = Scene::getSphere().getRadius();

      _cameras[_active_cam]->move(
        GMlib::Vector2D<float>( 0.0f, ( delta )*scale/_cameras[_active_cam]->getViewportW() )
      );
    }

    // Zoom
    else if( e->modifiers() == Qt::NoModifier ) {

      GMlib::CameraIso *isocam = dynamic_cast<GMlib::CameraIso*>(_cameras[_active_cam]);
      if( !isocam ) {

        double scale;
        if( _cameras[_active_cam]->isLocked() )
          scale = _cameras[_active_cam]->getLockDist();
        else
          scale = Scene::getSphere().getRadius();

        _cameras[_active_cam]->move(( delta )*scale/_cameras[_active_cam]->getViewportH());
      }
      else {

        if( delta < 0 ) _cameras[_active_cam]->zoom( 1.05 );
        if( delta > 0 ) _cameras[_active_cam]->zoom( 0.95 );
      }
    }


    // Store coords
    _hid_prevX = x;
    _hid_prevY = y;

    // Update GL
    updateGL();
  }

} // END namespace GMQtExt





